using System.Reflection;
using System.Text;

namespace SpirV;

public class OperandType
{
	public virtual bool ReadValue(IReadOnlyList<uint> words, int index, out object value, out int wordsUsed)
	{
		// This returns the dynamic type
		value = GetType();
		wordsUsed = 1;
		return true;
	}
}

public class Literal : OperandType
{
}

public class LiteralNumber : Literal
{
}

// The SPIR-V JSON file uses only literal integers
public class LiteralInteger : LiteralNumber
{
	public override bool ReadValue(IReadOnlyList<uint> words, int index, out object value, out int wordsUsed)
	{
		value = words[index];
		wordsUsed = 1;
		return true;
	}
}

public class LiteralString : Literal
{
	public override bool ReadValue(IReadOnlyList<uint> words, int index, out object value, out int wordsUsed)
	{
		// This is just a fail-safe -- the loop below must terminate
		wordsUsed = 1;
		int bytesUsed = 0;
		byte[] bytes = new byte[(words.Count - index) * 4];
		for (int i = index; i < words.Count; ++i)
		{
			uint word = words[i];
			byte b0 = (byte)(word & 0xFF);
			if (b0 == 0)
			{
				break;
			}
			else
			{
				bytes[bytesUsed++] = b0;
			}

			byte b1 = (byte)((word >> 8) & 0xFF);
			if (b1 == 0)
			{
				break;
			}
			else
			{
				bytes[bytesUsed++] = b1;
			}

			byte b2 = (byte)((word >> 16) & 0xFF);
			if (b2 == 0)
			{
				break;
			}
			else
			{
				bytes[bytesUsed++] = b2;
			}

			byte b3 = (byte)(word >> 24);
			if (b3 == 0)
			{
				break;
			}
			else
			{
				bytes[bytesUsed++] = b3;
			}
			wordsUsed++;
		}

		value = Encoding.UTF8.GetString(bytes, 0, bytesUsed);
		return true;
	}
}

public class LiteralContextDependentNumber : Literal
{
	// This is handled during parsing by ConvertConstant
}

public class LiteralExtInstInteger : Literal
{
	public override bool ReadValue(IReadOnlyList<uint> words, int index, out object value, out int wordsUsed)
	{
		value = words[index];
		wordsUsed = 1;
		return true;
	}
}

public class LiteralSpecConstantOpInteger : Literal
{
	public override bool ReadValue(IReadOnlyList<uint> words, int index, out object value, out int wordsUsed)
	{
		List<ObjectReference> result = new List<ObjectReference>();
		for (int i = index; i < words.Count; i++)
		{
			ObjectReference objRef = new ObjectReference(words[i]);
			result.Add(objRef);
		}

		value = result;
		wordsUsed = words.Count - index;
		return true;
	}
}

public abstract class Parameter
{
	public abstract IReadOnlyList<OperandType> OperandTypes { get; }
}

public class ParameterFactory
{
	public virtual Parameter? CreateParameter(object value)
	{
		return null;
	}
}

public class EnumType<T> : EnumType<T, ParameterFactory>
	where T : unmanaged, Enum
{
};

public class EnumType<T, U> : OperandType
	where T : unmanaged, Enum
	where U : ParameterFactory, new()
{
	public override bool ReadValue(IReadOnlyList<uint> words, int index, out object value, out int wordsUsed)
	{
		int wordsUsedForParameters = 0;
		if (typeof(T).GetTypeInfo().GetCustomAttributes<FlagsAttribute>().Any())
		{
			Dictionary<uint, IReadOnlyList<object>> result = new Dictionary<uint, IReadOnlyList<object>>();
			foreach (T enumValue in Enum.GetValues<T>())
			{
				uint bit = enumValue.AsUInt32();
				// bit == 0 and words[0] == 0 handles the 0x0 = None cases
				if ((words[index] & bit) != 0 || (bit == 0 && words[index] == 0))
				{
					Parameter? p = parameterFactory_.CreateParameter(bit);
					if (p == null)
					{
						result.Add(bit, Array.Empty<object>());
					}
					else
					{
						object[] resultItems = new object[p.OperandTypes.Count];
						for (int j = 0; j < p.OperandTypes.Count; ++j)
						{
							p.OperandTypes[j].ReadValue(words, 1 + wordsUsedForParameters, out object pValue, out int pWordsUsed);
							wordsUsedForParameters += pWordsUsed;
							resultItems[j] = pValue;
						}
						result.Add(bit, resultItems);
					}
				}
			}
			value = new BitEnumOperandValue<T>(result);
		}
		else
		{
			object[] resultItems;
			Parameter? p = parameterFactory_.CreateParameter(words[index]);
			if (p == null)
			{
				resultItems = Array.Empty<object>();
			}
			else
			{
				resultItems = new object[p.OperandTypes.Count];
				for (int j = 0; j < p.OperandTypes.Count; ++j)
				{
					p.OperandTypes[j].ReadValue(words, 1 + wordsUsedForParameters, out object pValue, out int pWordsUsed);
					wordsUsedForParameters += pWordsUsed;
					resultItems[j] = pValue;
				}
			}
			value = new ValueEnumOperandValue<T>((T)(object)words[index], resultItems);
		}

		wordsUsed = wordsUsedForParameters + 1;
		return true;
	}

	private readonly U parameterFactory_ = new U();
}

public class IdScope : OperandType
{
	public override bool ReadValue(IReadOnlyList<uint> words, int index, out object value, out int wordsUsed)
	{
		value = (Scope)words[index];
		wordsUsed = 1;
		return true;
	}
}

public class IdMemorySemantics : OperandType
{
	public override bool ReadValue(IReadOnlyList<uint> words, int index, out object value, out int wordsUsed)
	{
		value = (MemorySemantics)words[index];
		wordsUsed = 1;
		return true;
	}
}

public class IdType : OperandType
{
	public override bool ReadValue(IReadOnlyList<uint> words, int index, out object value, out int wordsUsed)
	{
		value = words[index];
		wordsUsed = 1;
		return true;
	}
}

public class IdResult : IdType
{
	public override bool ReadValue(IReadOnlyList<uint> words, int index, out object value, out int wordsUsed)
	{
		value = new ObjectReference(words[index]);
		wordsUsed = 1;
		return true;
	}
}

public class IdResultType : IdType
{
}

public class IdRef : IdType
{
	public override bool ReadValue(IReadOnlyList<uint> words, int index, out object value, out int wordsUsed)
	{
		value = new ObjectReference(words[index]);
		wordsUsed = 1;
		return true;
	}
}

public class PairIdRefIdRef : OperandType
{
	public override bool ReadValue(IReadOnlyList<uint> words, int index, out object value, out int wordsUsed)
	{
		ObjectReference variable = new ObjectReference(words[index]);
		ObjectReference parent = new ObjectReference(words[index + 1]);
		value = new { Variable = variable, Parent = parent };
		wordsUsed = 2;
		return true;
	}
}

public class PairIdRefLiteralInteger : OperandType
{
	public override bool ReadValue(IReadOnlyList<uint> words, int index, out object value, out int wordsUsed)
	{
		ObjectReference type = new ObjectReference(words[index]);
		uint word = words[index + 1];
		value = new { Type = type, Member = word };
		wordsUsed = 2;
		return true;
	}
}

public class PairLiteralIntegerIdRef : OperandType
{
	public override bool ReadValue(IReadOnlyList<uint> words, int index, out object value, out int wordsUsed)
	{
		uint selector = words[index];
		ObjectReference label = new ObjectReference(words[index + 1]);
		value = new { Selector = selector, Label = label };
		wordsUsed = 2;
		return true;
	}
}
